#pragma once

#include "Logger.h"
#include "Layer.h"
#include "IpAddress.h"

/// @file

/// @namespace pcpp
/// @brief The main namespace for the PcapPlusPlus lib
namespace pcpp
{
	/// @class NtpLayer
	/// Represents a NTP (Network Time Protocol) layer
	///
	/// @brief The NTP packet consists of an integral number of 32-bit (4 octet) words in network byte order.
	/// The packet format consists of three components: the header itself, one or more optional extension fields (for
	/// v4), and an optional message authentication code (MAC). Currently the extension fields are not supported. The
	/// NTP header is:
	///
	/// @code{.unparsed}
	///  0                   1                   2                   3
	///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                         Root Delay                            |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                         Root Dispersion                       |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                          Reference ID                         |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                                                               |
	/// +                     Reference Timestamp (64)                  +
	/// |                                                               |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                                                               |
	/// +                      Origin Timestamp (64)                    +
	/// |                                                               |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                                                               |
	/// +                      Receive Timestamp (64)                   +
	/// |                                                               |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                                                               |
	/// +                      Transmit Timestamp (64)                  +
	/// |                                                               |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                                                               |
	/// .                                                               .
	/// .                Extension Field 1 (variable, only v4)          .
	/// .                                                               .
	/// |                                                               |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                                                               |
	/// .                                                               .
	/// .                Extension Field 1 (variable, only v4)          .
	/// .                                                               .
	/// |                                                               |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                          Key Identifier                       |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// |                                                               |
	/// |                   dgst (128 for v4, 64 for v3)                |
	/// |                                                               |
	/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	/// @endcode
	class NtpLayer : public Layer
	{
	private:
#pragma pack(push, 1)
		struct ntp_header
		{
#if (BYTE_ORDER == LITTLE_ENDIAN)
			/// 3-bit integer representing the mode
			uint8_t mode : 3,
			    /// 3-bit integer representing the NTP version number
			    version : 3,
			    /// LI Leap Indicator (leap): 2-bit integer warning of an impending leap second to be inserted or
			    /// deleted in the last minute of the current month
			    leapIndicator : 2;
#else
			/// LI Leap Indicator (leap): 2-bit integer warning of an impending leap second to be inserted or deleted in
			/// the last minute of the current month
			uint8_t leapIndicator : 2,
			    /// 3-bit integer representing the NTP version number
			    version : 3,
			    /// 3-bit integer representing the mode
			    mode : 3;
#endif
			/// 8-bit integer representing the stratum
			uint8_t stratum;
			/// Total round-trip delay to the reference clock, in log2 seconds.
			int8_t pollInterval,
			    /// 8-bit signed integer representing the precision of the system clock, in log2 seconds.
			    precision;
			/// Total round-trip delay to the reference clock, in NTP short format.
			uint32_t rootDelay,
			    /// Total dispersion to the reference clock, in NTP short format.
			    rootDispersion,
			    /// 32-bit code identifying the particular server or reference clock.  The interpretation depends on the
			    /// value in the stratum field.
			    referenceIdentifier;
			/// Time when the system clock was last set or corrected, in NTP timestamp format.
			uint64_t referenceTimestamp,
			    /// Time at the client when the request departed for the server, in NTP timestamp format.
			    originTimestamp,
			    /// Time at the client when the request departed for the server, in NTP timestamp format.
			    receiveTimestamp,
			    /// Time at the server when the response left for the client, in NTP timestamp format.
			    transmitTimestamp;
		};
#pragma pack(pop)
		static_assert(sizeof(ntp_header) == 48, "ntp_header size is not 48 bytes");

#pragma pack(push, 1)
		struct ntp_v3_auth
		{
			/// An integer identifying the cryptographic key used to generate the message-authentication code
			uint32_t keyID;
			/// This is an integer identifying the cryptographic key used to generate the message-authentication code.
			uint8_t dgst[8];  // 64 bit DES based
		};
#pragma pack(pop)
		static_assert(sizeof(ntp_v3_auth) == 12, "ntp_v3_auth size is not 12 bytes");

#pragma pack(push, 1)
		struct ntp_v4_auth_md5
		{
			/// 32-bit unsigned integer used by the client and server to designate a secret 128-bit MD5 key.
			uint32_t keyID;
			/// 128-bit MD5 hash
			uint8_t dgst[16];
		};
#pragma pack(pop)
		static_assert(sizeof(ntp_v4_auth_md5) == 20, "ntp_v4_auth_md5 size is not 20 bytes");

#pragma pack(push, 1)
		struct ntp_v4_auth_sha1
		{
			/// 32-bit unsigned integer used by the client and server to designate a secret 160-bit SHA1 key.
			uint32_t keyID;
			/// 160-bit SHA1 hash
			uint8_t dgst[20];
		};
#pragma pack(pop)
		static_assert(sizeof(ntp_v4_auth_sha1) == 24, "ntp_v4_auth_sha1 size is not 24 bytes");

		ntp_header* getNtpHeader() const
		{
			return reinterpret_cast<ntp_header*>(m_Data);
		}

	public:
		/// Warning of an impending leap second to be inserted or deleted in the last minute of the current month
		enum LeapIndicator
		{
			/// Normal, no leap second
			NoWarning = 0,
			/// Last minute of the day has 61 seconds
			Last61Secs,
			/// Last minute of the day has 59 seconds
			Last59Secs,
			/// Unknown (clock unsynchronized)
			Unknown
		};

		/// Representing the NTP association modes
		enum Mode
		{
			/// Reserved variable
			Reserved = 0,
			/// Symmetrically active
			SymActive,
			/// Symmetrically passive
			SymPassive,
			/// Client mode
			Client,
			/// Server mode
			Server,
			/// Broadcasting mode
			Broadcast,
			/// NTP control messages
			Control,
			/// Reserved for private use
			PrivateUse
		};

		/// 32-bit code identifying the particular server or reference clock.
		/// The interpretation depends on the value in the stratum field.
		enum class ClockSource : uint32_t
		{
			// NTPv4

			/// Geosynchronous Orbit Environment Satellite
			GOES = ('G') | ('O' << 8) | ('E' << 16) | ('S' << 24),
			/// Global Position System
			GPS = ('G') | ('P' << 8) | ('S' << 16),
			/// Galileo Positioning System
			GAL = ('G') | ('A' << 8) | ('L' << 16),
			/// Generic pulse-per-second
			PPS = ('P') | ('P' << 8) | ('S' << 16),
			/// Inter-Range Instrumentation Group
			IRIG = ('I') | ('R' << 8) | ('I' << 16) | ('G' << 24),
			/// LF Radio WWVB Ft. Collins, CO 60 kHz
			WWVB = ('W') | ('W' << 8) | ('V' << 16) | ('B' << 24),
			/// LF Radio DCF77 Mainflingen, DE 77.5 kHz
			DCF = ('D') | ('C' << 8) | ('F' << 16),
			/// LF Radio HBG Prangins, HB 75 kHz
			HBG = ('H') | ('B' << 8) | ('G' << 16),
			/// LF Radio MSF Anthorn, UK 60 kHz
			MSF = ('M') | ('S' << 8) | ('F' << 16),
			/// LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz
			JJY = ('J') | ('J' << 8) | ('Y' << 16),
			/// MF Radio LORAN C station, 100 kHz
			LORC = ('L') | ('O' << 8) | ('R' << 16) | ('C' << 24),
			/// MF Radio Allouis, FR 162 kHz
			TDF = ('T') | ('D' << 8) | ('F' << 16),
			/// HF Radio CHU Ottawa, Ontario
			CHU = ('C') | ('H' << 8) | ('U' << 16),
			/// HF Radio WWV Ft. Collins, CO
			WWV = ('W') | ('W' << 8) | ('V' << 16),
			/// HF Radio WWVH Kauai, HI
			WWVH = ('W') | ('W' << 8) | ('V' << 16) | ('H' << 24),
			/// NIST telephone modem
			NIST = ('N') | ('I' << 8) | ('S' << 16) | ('T' << 24),
			/// NIST telephone modem
			ACTS = ('A') | ('C' << 8) | ('T' << 16) | ('S' << 24),
			/// USNO telephone modem
			USNO = ('U') | ('S' << 8) | ('N' << 16) | ('O' << 24),
			/// European telephone modem
			PTB = ('P') | ('T' << 8) | ('B' << 16),
			/// Multi Reference Sources
			MRS = ('M') | ('R' << 8) | ('S' << 16),
			/// Inter Face Association Changed
			XFAC = ('X') | ('F' << 8) | ('A' << 16) | ('C' << 24),
			/// Step time change
			STEP = ('S') | ('T' << 8) | ('E' << 16) | ('P' << 24),
			/// Google Refid used by Google NTP servers as time4.google.com
			GOOG = ('G') | ('O' << 8) | ('O' << 16) | ('G' << 24),
			/// Meinberg DCF77 with amplitude modulation (Ref:
			/// https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			DCFa = ('D') | ('C' << 8) | ('F' << 16) | ('a' << 24),
			/// Meinberg DCF77 with phase modulation)/pseudo random phase modulation (Ref:
			/// https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			DCFp = ('D') | ('C' << 8) | ('F' << 16) | ('p' << 24),
			/// Meinberg GPS (with shared memory access) (Ref:
			/// https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			GPSs = ('G') | ('P' << 8) | ('S' << 16) | ('s' << 24),
			/// Meinberg GPS (with interrupt based access) (Ref:
			/// https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			GPSi = ('G') | ('P' << 8) | ('S' << 16) | ('i' << 24),
			/// Meinberg GPS/GLONASS (with shared memory access) (Ref:
			/// https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			GLNs = ('G') | ('L' << 8) | ('N' << 16) | ('s' << 24),
			/// Meinberg GPS/GLONASS (with interrupt based access) (Ref:
			/// https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			GLNi = ('G') | ('L' << 8) | ('N' << 16) | ('i' << 24),
			/// Meinberg Undisciplined local clock (Ref: https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			LCL = ('L') | ('C' << 8) | ('L' << 16),
			/// Meinberg Undisciplined local clock (Ref: https://www.meinbergglobal.com/english/info/ntp-refid.htm)
			LOCL = ('L') | ('O' << 8) | ('C' << 16) | ('L' << 24),

			// NTPv3

			/// DCN routing protocol
			DCN = ('D') | ('C' << 8) | ('N' << 16),
			/// TSP time protocol
			TSP = ('T') | ('S' << 8) | ('P' << 16),
			/// Digital Time Service
			DTS = ('D') | ('T' << 8) | ('S' << 16),
			/// Atomic clock (calibrated)
			ATOM = ('A') | ('T' << 8) | ('O' << 16) | ('M' << 24),
			/// VLF radio (OMEGA, etc.)
			VLF = ('V') | ('L' << 8) | ('F' << 16)
		};

		/// 32-bit Kiss of Death (KoD) codes
		enum class KissODeath : uint32_t
		{
			/// The association belongs to a anycast server
			ACST = ('A') | ('C' << 8) | ('S' << 16) | ('T' << 24),
			/// Server authentication failed
			AUTH = ('A') | ('U' << 8) | ('T' << 16) | ('H' << 24),
			/// Autokey sequence failed
			AUTO = ('A') | ('U' << 8) | ('T' << 16) | ('O' << 24),
			/// The association belongs to a broadcast server
			BCST = ('B') | ('C' << 8) | ('S' << 16) | ('T' << 24),
			/// Cryptographic authentication or identification failed
			CRYP = ('C') | ('R' << 8) | ('Y' << 16) | ('P' << 24),
			/// Access denied by remote server
			DENY = ('D') | ('E' << 8) | ('N' << 16) | ('Y' << 24),
			/// Lost peer in symmetric mode
			DROP = ('D') | ('R' << 8) | ('O' << 16) | ('P' << 24),
			/// Access denied due to local policy
			RSTR = ('R') | ('S' << 8) | ('T' << 16) | ('R' << 24),
			/// The association has not yet synchronized for the first time
			INIT = ('I') | ('N' << 8) | ('I' << 16) | ('T' << 24),
			/// The association belongs to a manycast server
			MCST = ('M') | ('C' << 8) | ('S' << 16) | ('T' << 24),
			/// No key found.  Either the key was never installed or is not trusted
			NKEY = ('N') | ('K' << 8) | ('E' << 16) | ('Y' << 24),
			/// Rate exceeded.  The server has temporarily denied access because the client exceeded the rate threshold
			RATE = ('R') | ('A' << 8) | ('T' << 16) | ('E' << 24),
			/// Somebody is tinkering with the association from a remote host running ntpdc.  Not to worry unless some
			/// rascal has stolen your keys
			RMOT = ('R') | ('M' << 8) | ('O' << 16) | ('T' << 24),
			/// A step change in system time has occurred, but the association has not yet resynchronized
			STEP = ('S') | ('T' << 8) | ('E' << 16) | ('P' << 24),
		};

		/// A constructor that creates the layer from an existing packet raw data
		/// @param[in] data A pointer to the raw data
		/// @param[in] dataLen Size of the data in bytes
		/// @param[in] prevLayer A pointer to the previous layer
		/// @param[in] packet A pointer to the Packet instance where layer will be stored in
		NtpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
		    : Layer(data, dataLen, prevLayer, packet, NTP)
		{}

		/// Empty c'tor
		NtpLayer();

		/// @return The leap indicator
		LeapIndicator getLeapIndicator() const;

		/// Set the leap indicator
		void setLeapIndicator(LeapIndicator val);

		/// @return The version of NTP
		uint8_t getVersion() const;

		/// Set the version of NTP
		void setVersion(uint8_t val);

		/// @return The mode value
		Mode getMode() const;

		/// @return The mode as string
		std::string getModeString() const;

		/// Set the mode
		void setMode(Mode val);

		/// @return The value of stratum
		uint8_t getStratum() const;

		/// Set the value of stratum
		void setStratum(uint8_t val);

		/// @return The value of poll interval in log2 seconds
		int8_t getPollInterval() const;

		/// Set the value of poll interval
		/// @param[in] val Poll interval in log2 seconds
		void setPollInterval(int8_t val);

		/// @return The value of poll interval in seconds
		double getPollIntervalInSecs() const;

		/// @return The value of precision in log2 seconds
		int8_t getPrecision() const;

		/// Set the value of precision
		/// @param[in] val Precision in log2 seconds
		void setPrecision(int8_t val);

		/// @return The value of precision in seconds
		double getPrecisionInSecs() const;

		/// @return The value of root delay in NTP short format
		uint32_t getRootDelay() const;

		/// Set the value of root delay
		/// @param[in] val Root delay in NTP short format
		void setRootDelay(uint32_t val);

		/// @return The value of root delay in seconds
		double getRootDelayInSecs() const;

		/// Set the value of root delay
		/// @param[in] val Root delay in seconds
		void setRootDelayInSecs(double val);

		/// @return The value of root dispersion in NTP short format
		uint32_t getRootDispersion() const;

		/// Set the value of root delay
		/// @param[in] val Root dispersion in NTP short format
		void setRootDispersion(uint32_t val);

		/// @return The value of root dispersion in seconds
		double getRootDispersionInSecs() const;

		/// Set the value of root dispersion
		/// @param[in] val Root dispersion in seconds
		void setRootDispersionInSecs(double val);

		/// @return The value of reference identifier
		uint32_t getReferenceIdentifier() const;

		/// Set the value of reference identifier
		/// @param[in] val Value of the reference identifier as IPv4 address
		void setReferenceIdentifier(IPv4Address val);

		/// Set the value of reference identifier
		/// @param[in] val Value of the reference identifier as ClockSource
		void setReferenceIdentifier(ClockSource val);

		/// Set the value of reference identifier
		/// @param[in] val Value of the reference identifier as Kiss-O-Death code
		void setReferenceIdentifier(KissODeath val);

		/// @return The value of reference identifier as a string. String representation of NTP clock source if
		/// stratum is 1, IPv4 address or MD5 hash of first four octets of IPv6
		std::string getReferenceIdentifierString() const;

		/// @return The value of reference timestamp in NTP timestamp format
		uint64_t getReferenceTimestamp() const;

		/// Set the value of reference timestamp
		/// @param[in] val Timestamp in NTP timestamp format
		void setReferenceTimestamp(uint64_t val);

		/// @return The value of reference timestamp in seconds from Unix Epoch (1 Jan 1970)
		double getReferenceTimestampInSecs() const;

		/// Set the value of reference timestamp
		/// @param[in] val Value in seconds from Unix Epoch (1 Jan 1970)
		void setReferenceTimestampInSecs(double val);

		/// @return The reference timestamp value as readable string in ISO8601 format
		std::string getReferenceTimestampAsString();

		/// @return The value of origin timestamp in NTP timestamp format
		uint64_t getOriginTimestamp() const;

		/// Set the value of origin timestamp
		/// @param[in] val Value in NTP timestamp format
		void setOriginTimestamp(uint64_t val);

		/// @return The value of origin timestamp in seconds from Unix Epoch (1 Jan 1970)
		double getOriginTimestampInSecs() const;

		/// Set the value of origin timestamp
		/// @param[in] val Value in seconds from Unix Epoch (1 Jan 1970)
		void setOriginTimestampInSecs(double val);

		/// @return the origin timestamp value as readable string in ISO8601 format
		std::string getOriginTimestampAsString();

		/// @return The value of receive timestamp in NTP timestamp format
		uint64_t getReceiveTimestamp() const;

		/// Set the value of receive timestamp
		/// @param[in] val Value in NTP timestamp format
		void setReceiveTimestamp(uint64_t val);

		/// @return The value of receive timestampin seconds from Unix Epoch (1 Jan 1970)
		double getReceiveTimestampInSecs() const;

		/// Set the value of receive timestamp
		/// @param[in] val Value in seconds from Unix Epoch (1 Jan 1970)
		void setReceiveTimestampInSecs(double val);

		/// @return The receive timestamp value as readable string in ISO8601 format
		std::string getReceiveTimestampAsString();

		/// @return The value of transmit timestamp in NTP timestamp format
		uint64_t getTransmitTimestamp() const;

		/// Set the value of transmit timestamp
		/// @param[in] val Value in NTP timestamp format
		void setTransmitTimestamp(uint64_t val);

		/// @return The value of transmit timestamp in seconds from Unix Epoch (1 Jan 1970)
		double getTransmitTimestampInSecs() const;

		/// Set the value of transmit timestamp
		/// @param[in] val Value in seconds from Unix Epoch (1 Jan 1970)
		void setTransmitTimestampInSecs(double val);

		/// @return The transmit timestamp value as readable string in ISO8601 format
		std::string getTransmitTimestampAsString();

		/// @return Returns the key identifier if exists, returns 0 on unsupported NTP version or key identifier not
		/// found
		uint32_t getKeyID() const;

		/// @return Get the digest value as hexadecimal string, empty string on unsupported version
		std::string getDigest() const;

		/// Convert NTP short format to seconds from the Unix Epoch
		/// @param[in] val Value in NTP short format
		/// @return Value in seconds from Unix Epoch (1 Jan 1970)
		static double convertFromShortFormat(const uint32_t val);

		/// Convert NTP timestamp format to seconds from the Unix Epoch
		/// @param[in] val Value in NTP timestamp format
		/// @return Value in seconds from Unix Epoch (1 Jan 1970)
		static double convertFromTimestampFormat(const uint64_t val);

		/// Convert seconds from the Unix Epoch to NTP short format
		/// @param[in] val Value in seconds from Unix Epoch (1 Jan 1970)
		/// @return Value in NTP short format
		static uint32_t convertToShortFormat(const double val);

		/// Convert seconds from the Unix Epoch to NTP timestamp format
		/// @param[in] val Value in seconds from Unix Epoch (1 Jan 1970)
		/// @return Value in NTP timestamp format
		static uint64_t convertToTimestampFormat(const double val);

		/// A static method to convert timestamp value to ISO8601 date time format
		/// @param[in] timestamp Value in seconds from the Unix Epoch
		/// @return std::string ISO8601 formatted string
		static std::string convertToIsoFormat(const double timestamp);

		/// A static method to convert timestamp value to ISO8601 date time format
		/// @param[in] timestampInNTPformat Value in NTP timestamp format
		/// @return std::string ISO8601 formatted string
		static std::string convertToIsoFormat(const uint64_t timestampInNTPformat);

		/// A static method that takes a byte array and detects whether it is a NTP message
		/// @param[in] data A byte array
		/// @param[in] dataSize The byte array size (in bytes)
		/// @return True if the data is identified as NTP message
		static bool isDataValid(const uint8_t* data, size_t dataSize);

		/// A static method that checks whether the port is considered as NTP
		/// @param[in] port The port number to be checked
		static bool isNTPPort(uint16_t port)
		{
			return port == 123;
		}

		// overridden methods

		/// Parses the next layer. NTP is the always last so does nothing for this layer
		void parseNextLayer() override
		{}

		/// @return Get the size of the layer (Including the extension and authentication fields if exists)
		size_t getHeaderLen() const override
		{
			return m_DataLen;
		}

		/// Does nothing for this layer
		void computeCalculateFields() override
		{}

		/// @return The OSI layer level of NTP (Application Layer).
		OsiModelLayer getOsiModelLayer() const override
		{
			return OsiModelApplicationLayer;
		}

		/// @return Returns the protocol info as readable string
		std::string toString() const override;
	};
}  // namespace pcpp
